﻿Imports System.Drawing
Imports System.Windows.Forms
Imports System.Net.Sockets
Imports System.Threading
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary

Public Class CameraHost
#Region "Properties"
    ''' <summary>
    ''' Gets a value indicating whether this instance is listening.
    ''' </summary>
    ''' <value>
    ''' <c>true</c> if this instance is listening; otherwise, <c>false</c>.
    ''' </value>
    Public ReadOnly Property isListening() As Boolean
        Get
            Return Listens
        End Get
    End Property

    ''' <summary>
    ''' Gets or sets the size of the send buffer.
    ''' </summary>
    ''' <value>
    ''' The size of the send buffer.
    ''' </value>
    Public Property SendBufferSize() As Integer
        Get
            Return serverSocket.Server.SendBufferSize
        End Get
        Set(ByVal value As Integer)
            serverSocket.Server.SendBufferSize = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the size of the receive buffer.
    ''' </summary>
    ''' <value>
    ''' The size of the receive buffer.
    ''' </value>
    Public Property ReceiveBufferSize() As Integer
        Get
            Return serverSocket.Server.ReceiveBufferSize
        End Get
        Set(ByVal value As Integer)
            serverSocket.Server.ReceiveBufferSize = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets a value indicating whether [no delay].
    ''' </summary>
    ''' <value>
    '''   <c>true</c> if [no delay]; otherwise, <c>false</c>.
    ''' </value>
    Public Property NoDelay() As Boolean
        Get
            Return serverSocket.Server.NoDelay
        End Get
        Set(ByVal value As Boolean)
            serverSocket.Server.NoDelay = value
        End Set
    End Property
#End Region

#Region "Declarations"
    Dim serverSocket As TcpListener
    Dim clientSocket As TcpClient
    Dim netStream As NetworkStream
    Dim Context As SynchronizationContext
    Dim Listens As Boolean = False
    Dim PicBox As PictureBox

    Public Event OnConnection()
    Public Event LostConnection()
    Public Event errEncounter(ByVal ex As Exception)
#End Region

#Region "Methods"
    ''' <summary>
    ''' Initializes a new instance of the <see cref="CameraHost"/> class.
    ''' </summary>
    ''' <param name="Target">The target.</param>
    ''' <param name="Port">The port.</param>
    Public Sub New(ByVal Target As PictureBox, ByVal Port As Integer)
        serverSocket = New TcpListener(System.Net.IPAddress.Any, Port)
        Context = SynchronizationContext.Current()
        PicBox = Target
    End Sub

    ''' <summary>
    ''' Starts the connection.
    ''' </summary>
    Public Sub StartConnection()
        serverSocket.Start()
        Dim t As New Thread(AddressOf Main)
        t.Start()
    End Sub

    ''' <summary>
    ''' Closes the connection.
    ''' </summary>
    Public Sub CloseConnection()
        On Error Resume Next
        Listens = False
        If Not clientSocket Is Nothing Then clientSocket.Close()
        If Not netStream Is Nothing Then netStream.Close()
        If Not serverSocket Is Nothing Then serverSocket.Stop()
    End Sub

    ''' <summary>
    ''' Mains this instance.
    ''' </summary>
    Private Sub Main()
        Listens = True
GetConnection:  'Waits for an incoming connection
        Try
            clientSocket = serverSocket.AcceptTcpClient()
            EventRaise(Events.OnConnection)
            netStream = clientSocket.GetStream()
        Catch ex As Exception

        End Try

        While Listens AndAlso clientSocket.Connected
            Try
                Dim Formatter As IFormatter = New BinaryFormatter()
                Dim img As Image = Formatter.Deserialize(netStream)
                PicBox.Image = img
                PicBox.Invalidate()
            Catch ex As Exception 'If exception had occured a user disconnected
                EventRaise(Events.LostConnection)
                GoTo GetConnection 'Wait for another use
            End Try
        End While
        Context.Post(AddressOf CloseConnection, Nothing)
    End Sub

    ''' <summary>
    ''' Raise an Event.
    ''' </summary>
    ''' <param name="e">The e.</param>
    ''' <param name="Arg">The arg.</param>
    Private Sub EventRaise(ByVal e As Events, Optional ByVal Arg As Object = Nothing)
        Context.Post(AddressOf EventHandler, New EventArgs(e, Arg))
    End Sub

    ''' <summary>
    ''' Handles an Event.
    ''' </summary>
    ''' <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
    Private Sub EventHandler(ByVal e As EventArgs)
        Select Case e.EventT
            Case Events.OnConnection
                RaiseEvent OnConnection()
            Case Events.LostConnection
                RaiseEvent LostConnection()
        End Select
    End Sub
#End Region

End Class

Public Class CameraClient

#Region "API"
    ''' <summary>
    ''' Bits the BLT.
    ''' </summary>
    ''' <param name="hdcDest">The HDC dest.</param>
    ''' <param name="nXDest">The n X dest.</param>
    ''' <param name="nYDest">The n Y dest.</param>
    ''' <param name="nWidth">Width of the n.</param>
    ''' <param name="nHeight">Height of the n.</param>
    ''' <param name="hdcSrc">The HDC SRC.</param>
    ''' <param name="nXSrc">The n X SRC.</param>
    ''' <param name="nYSrc">The n Y SRC.</param>
    ''' <param name="dwRop">The dw rop.</param>
    ''' <returns></returns>
    Private Declare Function BitBlt Lib "gdi32.dll" (ByVal hdcDest As IntPtr, _
                                                 ByVal nXDest As Integer, _
                                                 ByVal nYDest As Integer, _
                                                 ByVal nWidth As Integer, _
                                                 ByVal nHeight As Integer, _
                                                 ByVal hdcSrc As IntPtr, _
                                                 ByVal nXSrc As Integer, _
                                                 ByVal nYSrc As Integer, _
                                                 ByVal dwRop As Int32) As Boolean
#End Region

#Region "Properties"
    ''' <summary>
    ''' Gets a value indicating whether this instance is connected.
    ''' </summary>
    ''' <value>
    ''' <c>true</c> if this instance is connected; otherwise, <c>false</c>.
    ''' </value>
    Public ReadOnly Property isConnected() As Boolean
        Get
            Return ConnectedHost
        End Get
    End Property

    ''' <summary>
    ''' Gets or sets the size of the send buffer.
    ''' </summary>
    ''' <value>
    ''' The size of the send buffer.
    ''' </value>
    Public Property SendBufferSize() As Integer
        Get
            Return clientSocket.SendBufferSize
        End Get
        Set(ByVal value As Integer)
            clientSocket.SendBufferSize = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the size of the receive buffer.
    ''' </summary>
    ''' <value>
    ''' The size of the receive buffer.
    ''' </value>
    Public Property ReceiveBufferSize() As Integer
        Get
            Return clientSocket.ReceiveBufferSize
        End Get
        Set(ByVal value As Integer)
            clientSocket.ReceiveBufferSize = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets a value indicating whether [no delay].
    ''' </summary>
    ''' <value>
    '''   <c>true</c> if [no delay]; otherwise, <c>false</c>.
    ''' </value>
    Public Property NoDelay() As Boolean
        Get
            Return clientSocket.NoDelay
        End Get
        Set(ByVal value As Boolean)
            clientSocket.NoDelay = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the size of the camera output.
    ''' </summary>
    ''' <value>
    ''' The size of the camera output.
    ''' </value>
    Public Property CameraOutputSize() As Size
        Get
            Return New Size(OutputWidth, OutputHeight)
        End Get
        Set(ByVal value As Size)
            OutputWidth = value.Width
            OutputHeight = value.Height
            'Reset()
        End Set
    End Property
#End Region

#Region "Declarations"
    Dim clientSocket As TcpClient
    Dim netStream As NetworkStream
    Dim Context As SynchronizationContext
    Dim ConnectedHost As Boolean = False
    Dim PicBox As PictureBox

    Dim CameraFPS As Integer = 30
    Public OutputHeight As Integer = 240
    Public OutputWidth As Integer = 360

    Public Event Connected()
    Public Event Disconnected()
    Public Event errEncounter(ByVal ex As Exception)
#End Region

#Region "Methods"

    ''' <summary>
    ''' Initializes a new instance of the <see cref="CameraClient"/> class.
    ''' </summary>
    ''' <param name="Target">The target.</param>
    Public Sub New(ByVal Target As PictureBox)
        clientSocket = New TcpClient()
        Context = SynchronizationContext.Current()
        PicBox = Target
    End Sub

    ''' <summary>
    ''' Connects the specified IP.
    ''' </summary>
    ''' <param name="IP">The IP.</param>
    ''' <param name="Port">The port.</param>
    Public Sub Connect(ByVal IP As String, ByVal Port As Integer)
        Try
            If IP <> "" Then
                clientSocket.Connect(IP, Port)
            End If
        Catch ex As Exception

        End Try

        If clientSocket.Connected Then
            RaiseEvent Connected()
            Dim t As New Thread(AddressOf Main)
            t.Start()
        End If
    End Sub

    ''' <summary>
    ''' Mains this instance.
    ''' </summary>
    Private Sub Main()
        ConnectedHost = True
        netStream = clientSocket.GetStream()
        While clientSocket.Connected And ConnectedHost
            Try
                Dim Bmp As Bitmap
                Bmp = CopyFrame(PicBox, New RectangleF(0, 0, CameraOutputSize.Width, CameraOutputSize.Height))
                Dim ms As New MemoryStream()
                Bmp.Save(ms, Imaging.ImageFormat.Jpeg) 'Change the format so we can pass a smaller image
                Dim Img As Image = Image.FromStream(ms)
                If SendImage(Img) = False Then Exit While
                Thread.Sleep(1000 / CameraFPS) 'Decreasing the time the calculation were taken (So each frame delay will be true)
            Catch ex As Exception
                Exit While
            End Try
        End While
        Context.Post(AddressOf Disconnect, Nothing)
    End Sub

    ''' <summary>
    ''' Disconnects this instance.
    ''' </summary>
    Public Sub Disconnect()
        On Error Resume Next
        If ConnectedHost Then
            ConnectedHost = False
            Dim SBufferSize, RBufferSize As Integer 'Get all of the current client socket properties so we can redeclare it with the previous settings (because it's being disposed)
            Dim NoDelay As Boolean
            SBufferSize = clientSocket.SendBufferSize
            RBufferSize = clientSocket.ReceiveBufferSize
            NoDelay = clientSocket.NoDelay
            clientSocket.Client.Disconnect(False)
            clientSocket.Close()
            clientSocket = New TcpClient()
            clientSocket.SendBufferSize = SBufferSize
            clientSocket.ReceiveBufferSize = RBufferSize
            clientSocket.NoDelay = NoDelay
            RaiseEvent Disconnected()
        End If
    End Sub

    ''' <summary>
    ''' Events the raise.
    ''' </summary>
    ''' <param name="e">The e.</param>
    ''' <param name="Arg">The arg.</param>
    Private Sub EventRaise(ByVal e As Events, ByVal Arg As Object)
        Context.Post(AddressOf EventHandler, New EventArgs(e, Arg))
    End Sub

    ''' <summary>
    ''' Events the handler.
    ''' </summary>
    ''' <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
    Private Sub EventHandler(ByVal e As EventArgs)
        Select Case e.EventT
            Case Events.Connected
                RaiseEvent Connected()
            Case Events.Disconnected
                RaiseEvent Disconnected()
        End Select
    End Sub
#End Region

#Region "Functions"
    ''' <summary>
    ''' Sends the image.
    ''' </summary>
    ''' <param name="img">The img.</param>
    ''' <returns></returns>
    Public Function SendImage(ByVal img As Image) As Boolean
        Try
            Dim Formatter As IFormatter = New BinaryFormatter()
            Formatter.Serialize(netStream, img)
            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Copies the frame.
    ''' </summary>
    ''' <param name="src">The SRC.</param>
    ''' <param name="rect">The rect.</param>
    ''' <returns></returns>
    Public Function CopyFrame(ByVal src As PictureBox, ByVal rect As RectangleF) As Bitmap
        Dim srcPic As Graphics = src.CreateGraphics
        Dim srcBmp As New Bitmap(src.Width, src.Height, srcPic)
        Dim srcMem As Graphics = Graphics.FromImage(srcBmp)
        Dim HDC1 As IntPtr = srcPic.GetHdc
        Dim HDC2 As IntPtr = srcMem.GetHdc
        Try
            BitBlt(HDC2, 0, 0, CInt(rect.Width), CInt(rect.Height), HDC1, CInt(rect.X), CInt(rect.Y), 13369376)

            CopyFrame = CType(srcBmp.Clone(), Bitmap)

            'Clean Up 
            srcPic.ReleaseHdc(HDC1)
            srcMem.ReleaseHdc(HDC2)
            srcPic.Dispose()
            srcMem.Dispose()
            Return CopyFrame
        Catch ex As Exception
            MessageBox.Show("Camera is not running!", "Camera is not running", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        End Try

        Return Nothing
    End Function
#End Region
End Class